/* --------------------------------------------------------------- */
/* class GUI_Scene, Scene representation in a GTK user interface.  */
/* Authors:                                                        */
/* Emilio Blazquez Sierra                                          */
/* Francisco Fernandez Castaño                                     */
/* Maria Jesus Ciudad Trujillo                                     */
/* --------------------------------------------------------------- */


#include "GUI_Scene.h"
#include "importar.h"


const float GUIScene::CLEAR_COLOR[4] = { 0.5, 0.5, 0.8, 1.0 };
const float GUIScene::CLEAR_DEPTH    = 1.0;

const float GUIScene::LIGHT0_POSITION[4] = { 0.0, 3.0, 3.0, 0.0 };
const float GUIScene::LIGHT0_AMBIENT[4]  = { 0.0, 0.0, 0.0, 1.0 };
const float GUIScene::LIGHT0_DIFFUSE[4]  = { 1.0, 1.0, 1.0, 1.0 };

const float GUIScene::LIGHT_MODEL_AMBIENT[4]       = { 0.2, 0.2, 0.2, 1.0 };
const float GUIScene::LIGHT_MODEL_LOCAL_VIEWER[1]  = { 0.0 };


void GUIScene::invalidate() 
{
  get_window()->invalidate_rect(get_allocation(), false);
}


void GUIScene::update()
{ get_window()->process_updates(false); }


GUIScene::GUIScene()
{
  m_Scale = 1;
  alpha_x = 0;
  alpha_y = 0;
  triangleselected = -1;

  //Creating camera and scene
  GLdouble position[3] = {0.0, 0.0, 5.0};
  GLdouble look[3] = {0.0, 0.0, 0.0};
  GLdouble up[3] = {0.0, 1.0, 0.0};
  Camera *camera = new Camera(position, look, up);
  scene = new Scene(camera);

  scene->set_state(NORMAL);

  // Creating basic models
  // basic_models.push_back(importer("./data/cube.obj"););
  
  basic_models.push_back(new Object3D("./data/cube.obj"));
  basic_models.push_back(new Object3D("./data/sphere.obj"));
  basic_models.push_back(new Object3D("./data/cone.obj"));
  basic_models.push_back(new Object3D("./data/cylinder.obj"));
  basic_models.push_back(new Object3D("./data/toroid.obj"));
  basic_models.push_back(new Object3D("./data/monkey.obj"));

  //
  // Configure OpenGL-capable visual.
  //

  Glib::RefPtr<Gdk::GL::Config> glconfig;

  // Try double-buffered visual
  glconfig = Gdk::GL::Config::create(Gdk::GL::MODE_RGB    |
				     Gdk::GL::MODE_DEPTH  |
				     Gdk::GL::MODE_DOUBLE);
  if (!glconfig) {
    std::cerr << "*** Cannot find the double-buffered visual.\n"
	      << "*** Trying single-buffered visual.\n";

    // Try single-buffered visual
    glconfig = Gdk::GL::Config::create(Gdk::GL::MODE_RGB   |
				       Gdk::GL::MODE_DEPTH);
    if (!glconfig) {
      std::cerr << "*** Cannot find any OpenGL-capable visual.\n";
      std::exit(1);
    }
  }

  //
  // Set OpenGL-capability to the widget.
  //

  set_gl_capability(glconfig);

  //
  // Add events.
  //
  add_events(Gdk::BUTTON1_MOTION_MASK    |
	     Gdk::BUTTON2_MOTION_MASK    |
	     Gdk::BUTTON3_MOTION_MASK    |
	     Gdk::BUTTON_PRESS_MASK      |
	     Gdk::BUTTON_RELEASE_MASK    |
	     Gdk::VISIBILITY_NOTIFY_MASK);
  
}

bool GUIScene::frustum(int w, int h)
{
  glViewport(0, 0, w, h);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  
  gluPerspective(60.0, 1.0, 0.0001, 1000.0);
  
  glMatrixMode(GL_MODELVIEW);
}

bool GUIScene::on_button_press_event(GdkEventButton* event)
{
  m_BeginX = event->x;
  m_BeginY = event->y;

  if (event->button == 3) {

    cout << "Mouse button 3 pressed at " << m_BeginX << " " << m_BeginY << endl;
     select(m_BeginX, get_height()-m_BeginY);
  }

  // don't block
  return false;
}


 
bool GUIScene::on_motion_notify_event(GdkEventMotion* event)
{
  float w = get_width();
  float h = get_height();
  float x = event->x;
  float y = event->y;
  bool redraw = false;

  // Rotation.

  if (event->state & GDK_BUTTON1_MASK) {
    
    float m_DX = (x - m_BeginX)*0.15;
    float m_DY = (y - m_BeginY)*0.15;
   
    alpha_x+=m_DX;
    alpha_y+=m_DY;
   
    redraw = true;

  }

  // Scaling.
  if (event->state & GDK_BUTTON2_MASK) {
    m_Scale = m_Scale * (1.0 + (y - m_BeginY) / h);
    if (m_Scale > SCALE_MAX)
      m_Scale = SCALE_MAX;
    else if (m_Scale < SCALE_MIN)
      m_Scale = SCALE_MIN;
    
    redraw = true;
  }

  m_BeginX = x;
  m_BeginY = y;

  if (redraw)
    invalidate();

  // don't block
  return false;
}


void GUIScene::on_realize()
{
  // We need to call the base on_realize()
  Gtk::DrawingArea::on_realize();

  //
  // Get GL::Drawable.
  //

  Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();

  //
  // GL calls.
  //

  // *** OpenGL BEGIN ***
  if (!gldrawable->gl_begin(get_gl_context()))
    return;

  glClearColor(CLEAR_COLOR[0], CLEAR_COLOR[1], CLEAR_COLOR[2], CLEAR_COLOR[3]);
  glClearDepth(CLEAR_DEPTH);

  glLightfv(GL_LIGHT0, GL_POSITION, LIGHT0_POSITION);
  glLightfv(GL_LIGHT0, GL_AMBIENT,  LIGHT0_AMBIENT);
  glLightfv(GL_LIGHT0, GL_DIFFUSE,  LIGHT0_DIFFUSE);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, LIGHT_MODEL_AMBIENT);
  glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, LIGHT_MODEL_LOCAL_VIEWER);

  glFrontFace(GL_CW);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);

  gldrawable->gl_end();
  // *** OpenGL END ***
}

bool GUIScene::on_configure_event(GdkEventConfigure* event)
{
  //
  // Get GL::Drawable.
  //

  Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();

  //
  // GL calls.
  //

  // *** OpenGL BEGIN ***
  if (!gldrawable->gl_begin(get_gl_context()))
    return false;

  frustum(get_width(), get_height());

  gldrawable->gl_end();
  // *** OpenGL END ***

  return true;
}

bool GUIScene::on_expose_event(GdkEventExpose* event)
{
  return draw();
}

bool GUIScene::on_unmap_event(GdkEventAny* event)
{
  idle_remove();
  
  return true;
}

bool GUIScene::on_idle()
{
  // Invalidate whole window.
  invalidate();
  // Update window synchronously (fast).
  update();

  return true;
}

void GUIScene::idle_add()
{
  if (!m_ConnectionIdle.connected())
    m_ConnectionIdle = Glib::signal_idle().connect(sigc::mem_fun(*this, &GUIScene::on_idle), GDK_PRIORITY_REDRAW);
}

void GUIScene::idle_remove()
{
  if (m_ConnectionIdle.connected())
    m_ConnectionIdle.disconnect();
}

Gtk::Menu* GUIScene::create_add_menu()
{

  // Shapes submenu
  Gtk::Menu* shapes_menu = Gtk::manage(new Gtk::Menu());
  {
    Gtk::Menu::MenuList& menu_list = shapes_menu->items();

    // Cube
    menu_list.push_back(Gtk::Menu_Helpers::MenuElem("Cube",sigc::bind(sigc::mem_fun(*this, &GUIScene::add),CUBE)));

    // Sphere
    menu_list.push_back(Gtk::Menu_Helpers::MenuElem("Sphere",sigc::bind(sigc::mem_fun(*this, &GUIScene::add),SPHERE)));

    // Cone
    menu_list.push_back(Gtk::Menu_Helpers::MenuElem("Cone",sigc::bind(sigc::mem_fun(*this, &GUIScene::add),CONE)));

    // Cylinder
    menu_list.push_back(Gtk::Menu_Helpers::MenuElem("Cylinder",sigc::bind(sigc::mem_fun(*this, &GUIScene::add),CYLINDER)));

    // Toroid
    menu_list.push_back(Gtk::Menu_Helpers::MenuElem("Toroid",sigc::bind(sigc::mem_fun(*this, &GUIScene::add),TOROID)));

    // Monkey
    menu_list.push_back(Gtk::Menu_Helpers::MenuElem("Monkey",sigc::bind(sigc::mem_fun(*this, &GUIScene::add),MONKEY)));

  }
  return shapes_menu;
}


void GUIScene::add(int shape)
{
  scene->add_object(basic_models[shape]);
  invalidate();
}

void GUIScene::DeleteAll()
{ 
  scene->dont_edit();
  int n=scene->get_objects().size();
  for(int i=0; i<n; i++)
    scene->delete_object(0);
  invalidate();
  

}

bool GUIScene::select(int x, int y)
{
  GLuint buff[64] = {0};
  GLint hits, view[4];
  int id;

  /*
    This choose the buffer where store the values for the selection data
  */
  glSelectBuffer(64, buff);

  /*
    This retrieves info about the viewport
  */
  glGetIntegerv(GL_VIEWPORT, view);

  /*
    Switching in selecton mode
  */
  glRenderMode(GL_SELECT);

  /*
    Clearing the names' stack
    This stack contains all the info about the objects
  */
  glInitNames();

  /*
    Now fill the stack with one element (or glLoadName will generate an error)
  */
  glPushName(0);

  /*
    Now modify the viewing volume, restricting selection area around the cursor
  */
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();

  /*
    restrict the draw to an area around the cursor
  */
  gluPickMatrix(x, y, 1.0, 1.0, view);
  gluPerspective(60, (float)view[2]/(float)view[3], 0.0001, 1000.0);

  /*
    Draw the objects onto the screen
  */
  glMatrixMode(GL_MODELVIEW);
  draw();

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  /*
    get number of objects drawed in that area
    and return to render mode
  */
  hits = glRenderMode(GL_RENDER);

  list_hits(hits, buff);

  glMatrixMode(GL_MODELVIEW);
  return true;
}


void GUIScene::list_hits(GLint hits, GLuint *names)
{
  int i;

  /*
    For each hit in the buffer are allocated 4 bytes:
    1. Number of hits selected (always one,
    beacuse when we draw each object
    we use glLoadName, so we replace the
    prevous name in the stack)
    2. Min Z
    3. Max Z
    4. Name of the hit (glLoadName)
  */

  float best_minz = INFINITE;
  int object = -1;

  cout << hits << " hits:" << endl;

  for (i = 0; i < hits; i++){
    cout << "hit: " << names[i * 4 + 3] << endl;

    if(names[i * 4 + 1] < best_minz){
      best_minz = names[i * 4 + 1];
      object = names[i * 4 + 3];
    }

  }

  if(object!=-1){
    cout << "Selected: " << object << endl;
    if(get_state() == NORMAL){
      if(scene->get_state_i(object)==NORMAL)
	scene->select_object(object);
      else
	scene->deselect_object(object);
      invalidate();
    }
    else if(triangleselected == -1){
      triangleselected = object;
      int objectselected = 0;
      int i=0;
      vector<Object3D*> objects = scene->get_objects();
      for (vector<Object3D *>::iterator it = objects.begin (); it != objects.end (); it ++)
	{
	  if ((*it)->get_state () != NORMAL){
	    objectselected = i;
	  }
	  i++;
	}

      t = scene->edit_face(objectselected, triangleselected);

      invalidate();
    }
    else{
      if(object==triangleselected){
      	scene->dont_edit_face();
      	triangleselected = -1;
	invalidate();
      }
	
    }
      
  }
  else cout << "nothing" << endl;
}

bool GUIScene::draw()
{
  //
  // Get GL::Drawable.
  //

  Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();

  //
  // GL calls.
  //

  // *** OpenGL BEGIN ***
  if (!gldrawable->gl_begin(get_gl_context()))
    return false;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glLoadIdentity();
  glShadeModel(GL_FLAT);

  // Place cam
  Camera *cam = scene->get_cam();
  cam->place();

  // Scaling and rotating
  glScalef(m_Scale, m_Scale, m_Scale);
  glRotatef(alpha_x, 0.0, 1.0, 0.0);
  glRotatef(alpha_y, 1.0, 0.0, 0.0);
  
  // Draw
  scene->draw();
  

  // Swap buffers.
  if (gldrawable->is_double_buffered())
    gldrawable->swap_buffers();
  else
    glFlush();
  gldrawable->gl_end();
  // *** OpenGL END ***

  return true;
}

void GUIScene::rotate (GLdouble degree, TAxis axis)
{
  if(get_state() == NORMAL)
    scene->rotate(degree, axis);
}
void GUIScene::scale (GLdouble factor, TAxis axis)
{
  if(get_state() == NORMAL)
    scene->scale(factor, axis);
}
void GUIScene::move (Vertex3D pos)
{
  if(get_state() == NORMAL)
    scene->move(pos);
}

void GUIScene::extrude(int dist)
{
  if(get_state() == EDITED && triangleselected!=-1)
    scene->extrude(t, dist);
}

int GUIScene::get_n_selected_objects () {
  return scene->get_n_selected_objects();
}

TState GUIScene::get_state(){
  return scene->get_state();
}

void GUIScene::edit_button()
{
  if(scene->get_state() == EDITED){
    scene->set_state(NORMAL);
    scene->dont_edit();
    triangleselected = -1;
  }
  else{
    triangleselected = -1;
    scene->set_state(EDITED);
  
    int selected = 0;
    int i=0;
    vector<Object3D*> objects = scene->get_objects();
    for (vector<Object3D *>::iterator it = objects.begin (); it != objects.end (); it ++)
      {
	if ((*it)->get_state () != NORMAL)
	  selected = i;
	i++;
      }
    
    scene->edit_object(selected);
    cout << "Editing object " << selected << endl;

  }

  invalidate();
}

void GUIScene::delete_objects()
{
  if(get_state() == NORMAL){
    vector<Object3D*> objects = scene->get_objects();
    int i=0;
    for (vector<Object3D *>::iterator it = objects.begin (); it != objects.end (); it ++){
      if(scene->get_state_i(i)==SELECTED){
	scene->deselect_object(i);
	scene->delete_object(i);
	i--; 
      }
      i++;
    }

    invalidate();
  }
    
}
void GUIScene::selectall()
{
  if(get_state() == NORMAL){
    scene->select_all();
    invalidate();
  }
}
void GUIScene::selectinverse()
{
  if(get_state() == NORMAL){
    scene->select_inverse();
    invalidate();
  }
}
void GUIScene::deselectall()
{
  if(get_state() == NORMAL){
    scene->deselect_all();
    invalidate();
  }
}
